package itpenguin;

import itpenguin.common.PMFactory;
import itpenguin.common.Util;
import itpenguin.entities.Article;
import itpenguin.entities.Content;
import itpenguin.entities.GeneralProperty;
import itpenguin.entities.ImageSrc;
import itpenguin.entities.Series;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import javax.jdo.PersistenceManager;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.bind.DatatypeConverter;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.Characters;
import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.XMLEvent;

import org.apache.commons.fileupload.FileItemIterator;
import org.apache.commons.fileupload.FileItemStream;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.fileupload.util.Streams;
import org.apache.commons.io.IOUtils;

import com.google.appengine.api.users.User;
import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;
import com.google.gdata.client.Query;
import com.google.gdata.client.photos.PicasawebService;
import com.google.gdata.data.OtherContent;
import com.google.gdata.data.PlainTextConstruct;
import com.google.gdata.data.photos.AlbumFeed;
import com.google.gdata.data.photos.PhotoEntry;
import com.google.gdata.data.photos.TagEntry;
import com.google.gdata.util.AuthenticationException;
import com.google.gdata.util.ContentType;
import com.google.gdata.util.ServiceException;

/**
 * Upload articles
 * @author hiro
 *
 */

public class RookeryServlet extends HttpServlet {

	private static final long serialVersionUID = 1L;
	private static final String EMPTY = "";
	private static final QName ID = new QName("id");
	private static final QName PUBLISHED = new QName("published");

	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {

		dispapatchToJsp(req, resp);
	}
	

	private void dispapatchToJsp(HttpServletRequest req,
			HttpServletResponse resp) throws ServletException, IOException {
		RequestDispatcher rd = req
				.getRequestDispatcher("/WEB-INF/pages/upload.jsp");
		rd.forward(req, resp);
	}

	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {

        ServletFileUpload upload = new ServletFileUpload();
		PersistenceManager pm = PMFactory.getInstance().getPersistenceManager();
		StringBuilder sb = new StringBuilder();
		String parentId = null;
		Article article = null;
		Series series = null;
		Map<String, byte[]> imageDic = new HashMap<String, byte[]>();
		String password = null;
		
		try {
			FileItemIterator iterator = upload.getItemIterator(req);
			while (iterator.hasNext()) {
				FileItemStream item = iterator.next();
				if (item.getFieldName().equals("parentId"))	{
					parentId = Streams.asString(item.openStream(), "UTF-8");
				} else if (item.getFieldName().equals("pwd")){
					password = Streams.asString(item.openStream(), "UTF-8");
				} else if (item.getName().endsWith("series.xml")) {
					series = makeSeries(item.openStream());
				} else if (item.getName().endsWith(".xml")) {
					article = makeArticle(item.openStream());
				} else if (item.getName().endsWith(".zip")) {
					
					ZipInputStream zis = new ZipInputStream(item.openStream());
					ZipEntry entry = null;
					while ((entry = zis.getNextEntry()) != null) {
						if (entry.getName().endsWith(".xml")) {
							byte[] buf = IOUtils.toByteArray(zis);
							article = makeArticle(new ByteArrayInputStream(buf));
						} else if (entry.getName().endsWith(".gif")
								|| entry.getName().endsWith(".png")
								|| entry.getName().endsWith(".jpg")
								|| entry.getName().endsWith(".jpeg")) {
							
							imageDic.put(entry.getName(), IOUtils.toByteArray(zis));
						}
						zis.closeEntry();
					}
				} else if (item.getName().endsWith(".txt") 
						|| item.getName().endsWith(".html")) {
					GeneralProperty gp = new GeneralProperty();
					gp.key = getBaseName(item.getName());
					gp.value = Streams.asString(item.openStream(), "UTF-8");
					sb.append("Uploaded general property (id:" + gp.key + ")");
					pm.makePersistent(gp);
				}
			}
			
			if (series != null) {
				
				if (parentId == null || parentId.equals("")) {
					Series oldSeries = Util.GetSeries(series.getId(), pm);
					if (oldSeries == null) {
						req.setAttribute("message", "new entry??");
						dispapatchToJsp(req, resp);
						return;
					}
					oldSeries.setDescription(series.getDescription());
					oldSeries.setTitle(series.getTitle());
				} else {
					// New entry
					if (! parentId.equals("root")) {
						if (Util.GetSeries(parentId) == null) {
							req.setAttribute("message", "invalid series id??");
							dispapatchToJsp(req, resp);
							return;
						}
						series.setParentId(parentId);
					}
					series.setPublished(new Date());
					pm.makePersistent(series);
				}
				sb.append("Updated series (id:" + series.getId() + ")");
				
			} else if (article != null) {
				
				PicasawebService service 
					= new PicasawebService("updateForITPenguin");
				service.useSsl();
				UserService userService 
					= UserServiceFactory.getUserService();
				User user = userService.getCurrentUser();
				service.setUserCredentials(user.getNickname(), password);
				String userId = System.getProperty("itpenguin.PicasaUserId");
				String albumId = System.getProperty("itpenguin.PicasaAlbumId");
				String feed = "https://picasaweb.google.com/data/feed/api/user/" + userId + "/albumid/" + albumId;
				
				if (parentId == null || parentId.equals("")) {
					// Update
					Article oldArticle  = Util.GetArticle(article.getId(), pm);
					if (oldArticle == null) {
						req.setAttribute("message", "new entry??");
						dispapatchToJsp(req, resp);
						return;
					}
					oldArticle.setDescription(article.getDescription());
					oldArticle.setPublished(article.getPublished());
					oldArticle.setTitle(article.getTitle());
					oldArticle.setSections(article.getSections());
					if (oldArticle.equals(article)) {
						oldArticle.setLastModified(new Date());
					}
					
					// Delete photos from picasa web album
					Query oldPhotQry = null;
					try {
						oldPhotQry = new Query(new URL(feed));
					} catch (MalformedURLException e) {
						throw new ServletException(e);
					}
					
					oldPhotQry.setStringCustomParameter("kind", "photo");
					oldPhotQry.setStringCustomParameter("tag", oldArticle.getId());

					AlbumFeed searchResultsFeed = null;
					searchResultsFeed = service.query(oldPhotQry, AlbumFeed.class);

					for (PhotoEntry photo : searchResultsFeed.getPhotoEntries()) {
						photo.delete();
					}	
					
					for (ImageSrc img : oldArticle.getImageMap()) {
						pm.deletePersistent(img);
					}
					article = oldArticle;
					
				} else {
					// New entry
					if (Util.GetSeries(parentId) == null) {
						req.setAttribute("message", "invalid series id??");
						dispapatchToJsp(req, resp);
						return;
					}
					article.setParentId(parentId);
					article.setLastModified(new Date());
					article.setPartNumber(Util.GetSeriesList(parentId, pm).size() + 1);
					pm.makePersistent(article);
				}
				
				// Upload new image into Picasa web album
				for(String key: imageDic.keySet()) {

					PhotoEntry newEntry = new PhotoEntry();
				    newEntry.setTitle(new PlainTextConstruct(key));
				    newEntry.setDescription(new PlainTextConstruct(article.getTitle()));
				    newEntry.setTimestamp(new Date());

				    OtherContent content = new OtherContent();
				    content.setBytes(imageDic.get(key));
				    if (key.endsWith(".gif")) {
				    	content.setMimeType(new ContentType("image/gif"));
				    } else if (key.endsWith(".png")) {
				    	content.setMimeType(new ContentType("image/png"));
				    } else if (key.endsWith(".jpg") || key.endsWith(".jpeg")) {
				    	content.setMimeType(new ContentType("image/jpeg"));
				    }
				    newEntry.setContent(content);
					
				    PhotoEntry updEntry = null;
					updEntry = service.insert(new URL(feed), newEntry);
				    
					TagEntry idTag = new TagEntry(); 
					idTag.setTitle(new PlainTextConstruct(article.getId()));
					String editLink = updEntry.getFeedLink().getHref();
					service.insert(new URL(editLink), idTag);
					
					ImageSrc imap = new ImageSrc(article, key);
					imap.setRemoteSrc(updEntry.getMediaThumbnails().get(1).getUrl());
					article.getImageMap().add(imap);
				}
				
				// Update published date
				Content c = article;
				while(c != null && c.getParentId() != null) {
					c = Util.GetSeries(c.getParentId(), pm);
					if (c.getPublished().compareTo(article.getPublished()) < 0) {
						c.setPublished(article.getPublished());
					}
				}
				
				sb.append("Updated article (id:" + article.getId() + ")");
			}
			pm.close();
		} catch (FileUploadException e) {
			throw new ServletException(e);
		} catch (XMLStreamException e) {
			throw new ServletException(e);
		} catch (AuthenticationException e) {
			throw new ServletException(e);
		} catch (ServiceException e) {
			throw new ServletException(e);
		}

		// Clear cache
		FlipperFilter.clearCache();
		
		req.setAttribute("message", sb.toString());
		dispapatchToJsp(req, resp);
	}

	private String getBaseName(String fullName) {
		
		if (fullName == null) return null;
		String[] split = fullName.split("[\\\\/]");
		String withExt = split[split.length - 1];
		return withExt.split("\\.")[0];
	}
	
	private Series makeSeries(InputStream is) throws XMLStreamException {
		
		XMLInputFactory factory = XMLInputFactory.newInstance();
		return makeSeries(factory.createXMLEventReader(is));
	}
	
	protected Series makeSeries(XMLEventReader r) throws XMLStreamException {
		
		Series series = new Series();
		String lastElementName = EMPTY;
		while (r.hasNext()) {
			XMLEvent e = r.nextEvent();
			if (e.isStartElement()) {
				StartElement element = (StartElement) e;
				lastElementName = element.getName().getLocalPart();
				if ("series".equals(lastElementName)) {
					series.setId(element.getAttributeByName(ID)
							.getValue());
				}
			} else if (e.isCharacters()
					&& !lastElementName.equals(EMPTY)) {
				Characters ch = (Characters) e;
				if (lastElementName.equals("title")) {
					series.setTitle(ch.getData());
				} else if (lastElementName.equals("description")) {
					series.setDescription(ch.getData());
				}
				lastElementName = EMPTY;
			}
		}
		return series;
	}
	
	private Article makeArticle(InputStream is) throws XMLStreamException {
		
		XMLInputFactory factory = XMLInputFactory.newInstance();
		return makeArticle(factory.createXMLEventReader(is));
	}
	
	protected Article makeArticle(XMLEventReader r) throws XMLStreamException {
		
		Article article = new Article();
		String lastElementName = EMPTY;
		
		while (r.hasNext()) {
			XMLEvent e = r.nextEvent();
			if (e.isStartElement()) {
				StartElement element = (StartElement) e;
				lastElementName = element.getName().getLocalPart();
				if ("article".equals(lastElementName)) {
					article.setId(element.getAttributeByName(ID)
							.getValue());
					String utcDate = element.getAttributeByName(PUBLISHED)
							.getValue();
					article.setPublished(DatatypeConverter
							.parseDateTime(utcDate).getTime());
				}
			} else if (e.isCharacters()
					&& !lastElementName.equals(EMPTY)) {
				Characters ch = (Characters) e;
				if (lastElementName.equals("title")) {
					article.setTitle(ch.getData());
				} else if (lastElementName.equals("description")) {
					article.setDescription(ch.getData());
				} else if (lastElementName.equals("section")) {
					article.getSections().add(ch.getData());
				}
				lastElementName = EMPTY;
			}
		}
		
		return article;
	}

}
